package com.suapi.suapigateway;

import com.suapi.suapiclientsdk.Until.SignUtils;
import com.suapi.suapicommon.model.constant.CommonConstant;
import com.suapi.suapicommon.model.entity.InterfaceInfo;
import com.suapi.suapicommon.model.entity.User;
import com.suapi.suapicommon.service.InnerInterfaceInfoService;
import com.suapi.suapicommon.service.InnerUserInterfaceInfoService;
import com.suapi.suapicommon.service.InnerUserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * 用户发送请求到 API 网关
 * 拦截请求，进行一些处理
 */

@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @DubboReference
    private InnerUserService innerUserService;
    @DubboReference
    private InnerUserInterfaceInfoService innerUserInterfaceInfoService;
    @DubboReference
    private InnerInterfaceInfoService innerInterfaceInfoService;
    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    public static final String SERVER_API_URL = "http://localhost:8090";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        InterfaceInfo interfaceInfo = null;
        User invokeUser = null;
        try {
            // 1.请求日志
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getPath().value();
            String method = request.getMethod().toString();
            log.info("请求唯一标识：{}", request.getId());
            log.info("请求路径：{}", path);
            log.info("请求方法：{}", method);
            log.info("请求参数：{}", request.getQueryParams());
            log.info("请求来源地址：{}", request.getRemoteAddress());
//        String sourceAddress = request.getLocalAddress().getHostString();
//        log.info("请求来源地址：{}", sourceAddress);
            log.info("请求头：{}", request.getHeaders());
            ServerHttpResponse response = exchange.getResponse();
            // TODO 黑白名单校验
            // 2.(黑白名单)
            // 3.用户鉴权(判断 ak、sk 是否合法)
            String accessKey = request.getHeaders().getFirst("accessKey");
            String nonce = request.getHeaders().getFirst("nonce");
            String timestamp = request.getHeaders().getFirst("timestamp");
            String sign = request.getHeaders().getFirst("sign");
            String body = request.getHeaders().getFirst("body");

            // 先校验时间戳是否真实
            // 检查 timestamp 是否为空
            if (timestamp == null || timestamp.isEmpty()) {
                return handleNoAuth(response); // 如果 timestamp 为空，直接返回未授权
            }
            long currentTimeMillis = System.currentTimeMillis() / 1000;
            long FIVE_MINUTES = 5 * 60L;
            // 判断时间戳是否过期
            if (currentTimeMillis - Long.parseLong(timestamp) > FIVE_MINUTES){
                return handleNoAuth(response);
            }
            // 校验随机数
            if (nonce == null || nonce.isEmpty()) {
                return handleNoAuth(response); // 如果 nonce 为空，直接返回未授权
            }
            ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
            // 判断redis中是否存在nonce的key
            if (!Boolean.TRUE.equals(redisTemplate.hasKey(CommonConstant.NONCE_KEY))) {
                addNonceToRedis(zSetOperations,nonce);
            }else{
                //存在该key，则要判断该key中是否存在该nonce
                Double score = zSetOperations.score(CommonConstant.NONCE_KEY, nonce);
                if (score == null) {
                    addNonceToRedis(zSetOperations, nonce);
                } else {
                    return handleNoAuth(response); // 如果 nonce 已存在，直接返回未授权
                }
            }
            //accessKey和secretKey从数据库中获取，这里直接写死，后面需要优化
            try {
                invokeUser = innerUserService.getInvokerUser(accessKey);
            } catch (Exception e) {
                log.info("innerUserService.getInvokerUser error" + e);
            }
            if (invokeUser == null){
                return handleNoAuth(response);
            }

            //校验server端生成的签名和客户端传过来的签名进行比对
            String secretKey = invokeUser.getSecretKey();
            if (!SignUtils.sign(body,secretKey).equals(sign)){
                return handleNoAuth(response);
            }
            // 4.通过PRC请求验证模拟接口是否存在
            try {
                interfaceInfo = innerInterfaceInfoService.getInterfaceInfo(SERVER_API_URL + path,method);
            } catch (Exception e) {
                log.info("innerInterfaceInfoService.getInterfaceInfo error" + e);
            }
            if (interfaceInfo == null){
                return handleNoAuth(response);
            }
            // 判断接口是否上线
            if (interfaceInfo.getStatus() == 0){
                return handleInvokeError(response);
            }
            // 检查用户是否被禁用
            int userInterfaceInfoStatus = innerUserInterfaceInfoService.getUserInterfaceInfoStatus(interfaceInfo.getId(), invokeUser.getId());
            if (userInterfaceInfoStatus == 1){
                return handleNoAuth(response);
            }
            // 检查调用次数
            int haveTimes = innerUserInterfaceInfoService.isHaveTimes(interfaceInfo.getId(), invokeUser.getId());
            if (haveTimes == 0 || haveTimes < 0){
                return handleNoAuth(response);
            }
            // 5.请求转发，调用模拟接口
            //return handleResponse(exchange, chain);
            return handleResponse(exchange, chain,interfaceInfo.getId(),invokeUser.getId(),haveTimes)
                    .doOnError(e -> log.error("异常在 handleResponse 中发生: ", e));
        } catch (NumberFormatException e) {
            log.error("网关异常处理.", e);
            return handleInvokeError(exchange.getResponse());
        }
        // 7.调用成功，接口调用次数 +1
        // 8.调用失败，返回一个规范的错误码
        //return chain.filter(exchange);
    }

    private void addNonceToRedis(ZSetOperations<String, Object> zSetOperations,String nonce) {

        try {
            zSetOperations.add(CommonConstant.NONCE_KEY, nonce, System.currentTimeMillis() / 1000);
        } catch (Exception e) {
            log.info("insert nonce to redis fail {}", e);
        }

    }

    /**
     * 处理响应
     *
     * @param exchange
     * @param chain
     * @return
     */
    public Mono<Void> handleResponse(ServerWebExchange exchange, GatewayFilterChain chain, long interfaceInfoId, long userId, int haveTimes) {
        try {
            // 获取原始的响应对象
            ServerHttpResponse originalResponse = exchange.getResponse();
            // 获取数据缓冲工厂
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            // 获取响应的状态码
            HttpStatus statusCode = (HttpStatus) originalResponse.getStatusCode();

            // 判断状态码是否为200 OK(按道理来说,现在没有调用,是拿不到响应码的,对这个保持怀疑 沉思.jpg)
            if(statusCode == HttpStatus.OK) {
                // 创建一个装饰后的响应对象(开始穿装备，增强能力)
                ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {

                    // 重写writeWith方法，用于处理响应体的数据
                    // 这段方法就是只要当我们的模拟接口调用完成之后,等它返回结果，
                    // 就会调用writeWith方法,我们就能根据响应结果做一些自己的处理
                    @Override
                    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                        log.info("body instanceof Flux: {}", (body instanceof Flux));
                        // 判断响应体是否是Flux类型
                        if (body instanceof Flux) {
                            Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                            // 返回一个处理后的响应体
                            // (这里就理解为它在拼接字符串,它把缓冲区的数据取出来，一点一点拼接好)
                            return super.writeWith(fluxBody.map(dataBuffer -> {
                                //TODO 调用成功，接口调用次数 +1
                                try {
                                    boolean b = innerUserInterfaceInfoService.invokeCount(interfaceInfoId, userId, haveTimes);
                                } catch (Exception e) {
                                    log.info("innerUserInterfaceInfoService.invokeCount error" + e);
                                }
                                // 读取响应体的内容并转换为字节数组
                                byte[] content = new byte[dataBuffer.readableByteCount()];
                                dataBuffer.read(content);
                                DataBufferUtils.release(dataBuffer);//释放掉内存
                                // 构建日志
                                StringBuilder sb2 = new StringBuilder(200);
                                List<Object> rspArgs = new ArrayList<>();
                                rspArgs.add(originalResponse.getStatusCode());
                                //rspArgs.add(requestUrl);
                                String data = new String(content, StandardCharsets.UTF_8);//data
                                sb2.append(data);
                                // 6.响应日志
                                log.info("响应结果：{}",data);
                                //log.info(sb2.toString(), rspArgs.toArray());
                                // log.info("<-- {} {}\n", originalResponse.getStatusCode(), data);
                                // 将处理后的内容重新包装成DataBuffer并返回
                                return bufferFactory.wrap(content);
                            }));
                        } else {
                            log.error("<--- {} 响应code异常", getStatusCode());
                        }
                        return super.writeWith(body);
                    }
                };
                // 对于200 OK的请求,将装饰后的响应对象传递给下一个过滤器链,并继续处理(设置repsonse对象为装饰过的)
                return chain.filter(exchange.mutate().response(decoratedResponse).build());
            }
            // 对于非200 OK的请求，直接返回，进行降级处理
            return chain.filter(exchange);
        }catch (Exception e){
            // 处理异常情况，记录错误日志
            log.error("网关异常处理.\n" + e);
            return chain.filter(exchange);
        }
    }


    private Mono<Void> handleInvokeError(ServerHttpResponse response){
        response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        return response.setComplete();
    }

    private Mono<Void> handleNoAuth(ServerHttpResponse response){
        response.setStatusCode(HttpStatus.FORBIDDEN);
        return response.setComplete();
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

